/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			EngGeometry.d
	Description:	engine geometry base class
	Date:			16.11.2007 by Digited
*/
module moonglide.core.engobjects.EngGeometry;

import
	moonglide.core.engobjects.EngObject,
	moonglide.core.engobjects.EngLight,

	moonglide.resources.resobjects.lowlevel.ResMesh,
	moonglide.resources.resobjects.hilevel.ResMaterial;


//============================================================
/// lod structure
//============================================================
struct SLod
{
	CResMesh	mesh;
	float		distance;

	static SLod opCall( CResMesh mesh_, float distance_ )
	{
		SLod lod;
		lod.mesh = mesh_;
		lod.distance = distance_;
		return lod;
	}

	int opCmp( SLod lod )
	{
		if( distance == lod.distance )
			return 0;
		else
			if( distance > lod.distance )
				return 1;
			else
				return -1;
	}
}


//=============================================================
/// CEngGeometry - renderable object with low-level data (arrays of vertices, tex coords, normals etc)
//=============================================================
class CEngGeometry : public CEngObject
{
	protected:
		int _activeLevel = -1;
		SLod[]		_lods;

		bool CalcLodLevel()
		{
			if( lodDistance == -1 )		/// don`t use LOD
			{
				if( _lods.length == 0 )
				{
					_activeLevel = -1;
					activeMesh = null;
					return false;
				}
			}

			static int newLevel;
			newLevel = ( _lods.length > 0 ) ? 0 : -1;

			foreach( index, lod; _lods )
				if( disToCam >= lod.distance )
					newLevel = index;

			if( _activeLevel == newLevel || newLevel >= _lods.length )
				return false;

			debug debugLog.trace( Format( "[{}] changes LOD level: {} -> {}", id, _activeLevel, newLevel ));

			_activeLevel = newLevel;
			activeMesh = _lods[ _activeLevel ].mesh;

			return true;
		}

		CResMaterial	_material;

	public:
		int lodDistance = 0; // -1 means not to use LOD system

		void addLod( CResMesh lod, float distance )
		{
			_lods ~= SLod( lod, distance );
			_lods.sort;

			if( lodDistance == -1 )
			{
                _activeLevel = 0;
                activeMesh = _lods[ _activeLevel ].mesh;
			}
		}


		void clearLods()
		{
		    foreach( inout lod; _lods )
				lod.mesh.release;
            _lods.length = 0;
		}


		CResMesh		activeMesh;
		CEngLight[]		lights;

		//============================================================
		/// a bit overriden update
		//============================================================
		override void update( float timeelapsed, ref Vector3f parentWPos, ref Matrix33f parentWOrient )
		{
			CalcLodLevel();
			super.update( timeelapsed, parentWPos, parentWOrient );
		}

		//============================================================
		/// get material
		//============================================================
		CResMaterial material()
		{
			if( !_material )
				_material = CResMaterial( _id ~ "_mat" );

			return _material;
		}

		//============================================================
		/// set material
		/// NOTE (Digited#): changed to property
		//============================================================
		void material( char[] mID )
		{
			CResMaterial mm = CResMaterial( mID );

			if( !_material )
				_material = mm;
			else
			{
				debug
				{
					if( mm )
						if( _material.id == mm.id )
							debugLog.warn( id ~ ": try to set already setted material <" ~ mm.id ~">. A bug?" );
				}

				_material.release;
				_material = mm;
			}
		}

		//------------------------------------------------------------------------------------------

		this( char[] idname )
		{
			super( idname );
		}


		~this()
		{
			debug debugLog.trace( "geom object destroy begin: " ~ _id );

			foreach( inout lod; _lods )
				lod.mesh.release;

			_lods = null;

			if( _material !is null )
				_material.release;

			_material = null;
		}
}
